Skip to content

Add net9 File.WriteAllBytes over a span - #606

Merged
SimonCropp merged 1 commit into
mainfrom
file-writeallbytes-span
Sep 10, 2026
Merged

Add net9 File.WriteAllBytes over a span#606
SimonCropp merged 1 commit into
mainfrom
file-writeallbytes-span

Conversation

@SimonCropp

Copy link
Copy Markdown
Owner

One member — File.WriteAllBytes(string, ReadOnlySpan<byte>).

Most of this family was already done

Diffing the whole byte-writing surface of File across the reference assemblies turned up eight members missing below net9, and seven of them are already polyfilled: AppendAllBytes in both the array and span forms, both AppendAllBytesAsync overloads, both WriteAllBytesAsync overloads, and ReadAllBytesAsync. WriteAllBytes(string, ReadOnlySpan<byte>) was the only real gap.

Delegating to the array overload, deliberately

The neighbouring polyfills in this file open their own FileStream (FileMode.Create, FileShare.None, and so on). This one does not, because it does not have to: File.WriteAllBytes(path, bytes.ToArray()) inherits the BCL's own file mode, sharing and validation rather than my guess at them.

Verified against net11 that the span overload really is indistinguishable from the array overload:

  • identical content for empty, one-byte, three-byte and 100KB payloads
  • the same truncation of a longer existing file, including down to zero length, with the file still present
  • the same creation of a missing file
  • the same exception for a null, empty or whitespace path, a missing directory, and a path that is a directory

So the delegation is faithful, and the cost is the array copy, which is //Note:d along with the fact that it is only reached with an explicit ReadOnlySpan — a byte[] argument binds to the BCL overload, as it does today.

A .NET Framework difference the tests had to accommodate

My first version pinned the empty-path failure as ArgumentException:path. That passed everywhere except net462, where File.WriteAllBytes("", bytes) throws ArgumentException with no paramName, while .NET names it path. That is a BCL difference between frameworks, and the polyfill correctly inherits whichever the running framework has — the equivalence assertion against the array overload passed on net462 all along; only my Core-specific hard-coded expectation failed.

The tests now assert full equivalence with the array overload on every framework, and pin concrete shapes only where they are stable everywhere (ArgumentNullException:path, DirectoryNotFoundException), checking just the exception type for the empty and whitespace cases. The comment records why.

Also fixed

The existing WriteAllBytesAsync(string, ReadOnlyMemory<byte>, CancellationToken) carried a //Link: pointing at appendallbytesasync. Corrected.

Verification

Solution clean in Release, Consume clean across all 22 TFMs, tests green on net11.0 (1745), net10.0 (1745), net9.0 (1745), net8.0 (1742), net462 (1691), plus PublicTests, EmbeddedTests, UnsafeTests, NoRefsTests and NoExtrasTests.

API count 1161 → 1162.

One member. Diffing the whole byte writing family showed the rest was already covered:
AppendAllBytes in both forms, both AppendAllBytesAsync overloads, both WriteAllBytesAsync
overloads and ReadAllBytesAsync are all polyfilled already. WriteAllBytes(string,
ReadOnlySpan<byte>) was the only gap.

Verified against net11 that the span overload is indistinguishable from the array
overload: identical content for empty, small and 100KB payloads, the same truncation of
a longer existing file, the same creation of a missing one, and the same exception for
a null, empty or whitespace path, a missing directory and a path that is a directory.
So the polyfill delegates to the array overload rather than opening its own FileStream,
which would have meant guessing at the file mode and sharing.

Also fixes the //Link: on the existing WriteAllBytesAsync(string, ReadOnlyMemory<byte>,
CancellationToken), which pointed at AppendAllBytesAsync.

API count 1161 -> 1162.
@SimonCropp SimonCropp added this to the 11.3.0 milestone Sep 10, 2026
@SimonCropp
SimonCropp merged commit e5af916 into main Sep 10, 2026
4 of 6 checks passed
@SimonCropp
SimonCropp deleted the file-writeallbytes-span branch September 10, 2026 10:33
This was referenced Sep 10, 2026
This was referenced Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant